package com.chalmers.foa.gamehandlers.impl;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

import com.chalmers.foa.gamemodel.gameobjects.other.PlayerModel;

public class InputHandler implements SensorEventListener {
	static final double FORCE_THRESH_HOLD = 1.5f;

	private static InputHandler instance;
	private PlayerModel player;
	private SensorManager sensorManager;
	private Sensor sensorAc, sensorGy;
	private static float m_totalForcePrev;
	private float[] accelerometerValues;
	private PropertyChangeSupport pcs;
	private float speedMultiplier;
	private float[] newValues;

	/**
	 * Get a singleton for the InputHandler
	 */
	public static InputHandler getInstance() {
		if (instance == null)
			instance = new InputHandler();
		return instance;
	}

	private InputHandler() {
		speedMultiplier = 1;
		accelerometerValues = new float[] { 0, 0, 0 };
		pcs = new PropertyChangeSupport(this);
	}

	public float[] getAccelerometerValues() {
		return accelerometerValues.clone();
	}

	/**
	 * Sets the InputHandler to contain the GameView
	 * 
	 * @param _view
	 *            The GameView used by the Game Activity
	 * @param _sensorManager
	 *            Manages all sensors
	 */
	public void setPlayer(PlayerModel _player) {
		player = _player;
	}

	public void addPropertyChangeListener(PropertyChangeListener listener) {
		this.pcs.addPropertyChangeListener(listener);
	}

	/**
	 * Sets accelerometer and gyroscope sensor listeners
	 */
	public void setSensorListener(SensorManager _sensorManager, boolean shake) {
		sensorManager = _sensorManager;

		if (sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER).size() != 0
				&& shake) {
			sensorAc = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER)
					.get(0);
			sensorManager.registerListener(this, sensorAc,
					SensorManager.SENSOR_DELAY_GAME);
		}

		if (sensorManager.getSensorList(Sensor.TYPE_ORIENTATION).size() != 0) {
			sensorGy = sensorManager.getSensorList(Sensor.TYPE_ORIENTATION)
					.get(0);
			sensorManager.registerListener(this, sensorGy,
					SensorManager.SENSOR_DELAY_GAME);
		}
	}

	public void stopSensorListener() {
		if (sensorManager != null) {
			sensorManager.unregisterListener(this);
		}
	}

	/**
	 * Handles the sensors
	 * 
	 * @param event
	 *            SensorEvent containing sensor information
	 */
	@Override
	public void onSensorChanged(SensorEvent event) {
		if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
			calculateShake(event.values);
		} else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
			calculateTilt(event.values);
		}
	}

	/**
	 * Calculates how hard the device is shaken
	 * 
	 * @param values
	 *            Contains sensor data
	 */
	private void calculateShake(float[] values) {
		float totalForce = 0.0f;
		totalForce += Math.pow(values[SensorManager.DATA_X]
				/ SensorManager.GRAVITY_EARTH, 2.0);
		totalForce += Math.pow(values[SensorManager.DATA_Y]
				/ SensorManager.GRAVITY_EARTH, 2.0);
		totalForce += Math.pow(values[SensorManager.DATA_Z]
				/ SensorManager.GRAVITY_EARTH, 2.0);
		totalForce = (float) Math.sqrt(totalForce);
		if ((totalForce < FORCE_THRESH_HOLD)
				&& (m_totalForcePrev > FORCE_THRESH_HOLD)) {
			pcs.firePropertyChange("Restart", null, null);
		}
		m_totalForcePrev = totalForce;
	}

	/**
	 * Calculates the tilt and gravity of the device
	 * 
	 * @param values
	 *            Contains sensor data
	 */
	private void calculateTilt(float[] values) {
		newValues = values.clone();
		if(newValues[1]>100)
			newValues[1]=-(newValues[1]-180);
		else if(newValues[1]<-100)
			newValues[1]=-(newValues[1]+180);
			
		if (player.checkJump() == PlayerModel.State.Jumping)
			player.setSpeed(-(int) (newValues[1] / (7) * speedMultiplier), player
					.getSpeed().y);
		if (player.getPlayerObject().getPreviousPosition().y != (player
				.getPlayerObject().getPosition().y))
			return;
		player.setSpeed(-(int) (newValues[1] / (7) * speedMultiplier), player
				.getSpeed().y);
		accelerometerValues = newValues;
	}

	public void setSpeedMultiplier(float _speedMultiplier) {
		speedMultiplier = _speedMultiplier;
	}

	public float getSpeedMultiplier() {
		return speedMultiplier;
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}

	/**
	 * Listener handling on screen touches
	 */
	public OnTouchListener screenTouchedListener = new OnTouchListener() {
		/**
		 * Handles screen touch events
		 * 
		 * @param event
		 *            MotionEvent containing information about the event
		 */
		@Override
		public boolean onTouch(View _view, MotionEvent event) {
			player.jump(PhysicsHandler.getInstance().getGravity());
			return true;
		}
	};

	public boolean onKeyDown(int keyCode, KeyEvent event) {
		switch (keyCode) {
		case KeyEvent.KEYCODE_DPAD_LEFT: {
			player.setSpeed((int)(-7*speedMultiplier), player.getSpeed().y);
			accelerometerValues[1]=49f;
			return true;
		}
		case KeyEvent.KEYCODE_DPAD_RIGHT: {
			player.setSpeed((int)(7*speedMultiplier), player.getSpeed().y);
			accelerometerValues[1]=-49f;
			return true;
		}
		case KeyEvent.KEYCODE_SPACE: {
			player.checkJump();
			player.jump(PhysicsHandler.getInstance().getGravity());
			return true;
		}
		case KeyEvent.KEYCODE_VOLUME_UP:
		case KeyEvent.KEYCODE_VOLUME_DOWN:
		case KeyEvent.KEYCODE_DPAD_CENTER: {
			if (player.getPlayerObject().getPreviousPosition().y == (player
					.getPlayerObject().getPosition().y))
				PhysicsHandler.getInstance().reverseGravity();
			return true;
		}
		case KeyEvent.KEYCODE_BACK:
			return true;
		}
		return false;
	}

	public boolean onKeyUp(int keyCode, KeyEvent event) {
		switch (keyCode) {
		case KeyEvent.KEYCODE_DPAD_LEFT: {
			player.setSpeed(0, player.getSpeed().y);
			accelerometerValues[1]=0f;
			return true;
		}
		case KeyEvent.KEYCODE_DPAD_RIGHT: {
			player.setSpeed(0, player.getSpeed().y);
			accelerometerValues[1]=0f;
			return true;
		}
		}
		return false;
	}

}
